﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Timers;

namespace VMS.FTS.APPPUSH.Log
{          
    /// <summary>
    /// VMS Logger system
    /// </summary>
    public class Logger
    {
        #region | Singleton construction |
        private static readonly Lazy<Logger> lazy =
            new Lazy<Logger>(() => new Logger());

        public static Logger Instance { get { return lazy.Value; } }
        #endregion

        #region | Contructor |
        /// <summary>
        /// Default constructor
        /// Init timer
        /// Init Log entry object
        /// </summary>
        public Logger()
        {
            // logging directory
            LogDir = Properties.Settings.Default.WORKING_DIRECTORY +
               Properties.Settings.Default.LOG_DIRECTORY;

            // create timer with timeout
            LogTimer = new Timer(LOG_TIMEOUT);
            // auto start timer when timeout
            LogTimer.AutoReset = true;
            // callback function
            LogTimer.Elapsed += LogTimer_Elapsed;
            // start timer
            LogTimer.Start();
            // create LogEntry object to hold log
            LogEntry = new List<string>();
            // call timerfunction for first time.
            LogTimer_Elapsed(LogTimer, null);
        }

        /// <summary>
        /// Save all log entry to file
        /// Check if there is need to create new log file
        /// </summary>
        /// <param name="sender">LogTimer</param>
        /// <param name="e"></param>
        void LogTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            // check Log avaible
            if (LogEntry.Count > 0)
            {
                // create new temp log entry
                List<String> tempEntry = new List<string>();
                lock (LogEntry)
                {
                    // copy log entry
                    tempEntry.AddRange(LogEntry);
                    // clear log
                    LogEntry.Clear();
                }

                // process log file
                DateTime dt = DateTime.Now;
                String filePath = logDirectory +"\\" +   dt.ToString("yyyyMMdd") + ".log";
                // If log file not exist then create new one
                if (!File.Exists(filePath))
                {
                    // create log file
                    FileStream fs = File.Create(filePath);
                    // close
                    fs.Close();
                }

                // write log entry to file
                try
                {
                    StreamWriter sw = File.AppendText(filePath);
                    // write each log entry per line
                    foreach (string str in tempEntry)
                        sw.WriteLine(str);
                    // flush stream
                    sw.Flush();
                    // close stream
                    sw.Close();
                }
                catch (Exception ex)
                {
                    // print exception if error
                    Console.WriteLine(ex.Message.ToString());
                }
            }

        }
        #endregion

        #region | Methods |
        /// <summary>
        /// Append string to log file
        /// </summary>
        public void append(String source,String message, LOG_LEVEL level)
        {
            LOG_LEVEL logLevel = LOG_LEVEL.OFF;
            // Convert log level string to LOG_LEVEL enum
            switch (strLogLevel)
            {
                case "ALL":
                    logLevel = LOG_LEVEL.ALL;
                    break;

                case "ERROR":
                    logLevel = LOG_LEVEL.ERROR;
                    break;

                case "INFO":
                    logLevel = LOG_LEVEL.INFO;
                    break;
                case "DEBUG":
                    logLevel = LOG_LEVEL.DEBUG;
                    break;
                default:
                    logLevel = LOG_LEVEL.OFF;
                    break;
            }

            // check if log level > current level then add to log entry
            if (logLevel >= level)
            {
                string logLv = GetEnumDescription(level);
                LogEntry.Add(string.Format("{0,-9}| {1,-6}| {2,-20}| {3}",
                                DateTime.Now.ToString("hh:mm:ss"), level, source, message));
            }
        }
        #endregion

        #region | Fields |
        /// <summary>
        /// Log level
        /// </summary>
        private String strLogLevel = String.Empty;
        public String LogLevel
        {
            get { return strLogLevel; }
            set { strLogLevel = value; }
        }

        /// <summary>
        /// Log directory
        /// </summary>
        private String logDirectory = String.Empty;
        public String LogDir
        {
            get { return logDirectory; }
            set { logDirectory = value; }
        }

        List<String> LogEntry;
        Timer LogTimer;

        const int LOG_TIMEOUT = 5000;
        #endregion

        #region | Enums |
        public static string GetEnumDescription(Enum value)
        {
            FieldInfo fi = value.GetType().GetField(value.ToString());

            DescriptionAttribute[] attributes =
                (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attributes != null && attributes.Length > 0)
                return attributes[0].Description;
            else
                return value.ToString();
        }
        #endregion
    }
    
    /// <summary>
    /// VMS Log level enum
    /// </summary>
    public enum LOG_LEVEL
    {
        [Description("ALL")]
        ALL = 100,
        [Description("INFO")]
        INFO = 75,
        [Description("ERROR")]
        ERROR = 50,
        [Description("DEBUG")]
        DEBUG = 25,
        [Description("OFF")]
        OFF = 0,
    }

    public abstract class ILogable
    {
        protected string LOG_TAG;
        protected void LOG(string message, LOG_LEVEL level)
        {
            Logger.Instance.append(LOG_TAG, message, level);
#if DEBUG
            LOG_LEVEL baseLevel = LOG_LEVEL.DEBUG;
            if (level > baseLevel)
            {
                if (level == LOG_LEVEL.ERROR)
                    Console.ForegroundColor = ConsoleColor.Red;
                else if (level == LOG_LEVEL.INFO)
                    Console.ForegroundColor = ConsoleColor.Yellow;
                else
                    Console.ForegroundColor = ConsoleColor.White;

                if (Console.CursorLeft != 0)
                    Console.WriteLine();
                Console.WriteLine(string.Format("{0,-9}| {1,-6}| {2,-20}| {3}",
                                DateTime.Now.ToString("hh:mm:ss"), level, LOG_TAG, message));

                Console.ForegroundColor = ConsoleColor.White;
            }
#endif
        }

        protected void LOG(string function, string message, LOG_LEVEL level,bool newline = true)
        {
            message = string.Format("{0,-20}| {1}", function, message);
            Logger.Instance.append(LOG_TAG, message, level);
#if DEBUG
            LOG_LEVEL baseLevel = LOG_LEVEL.OFF;
            if (level > baseLevel)
            {
                if (level == LOG_LEVEL.ERROR)
                    Console.ForegroundColor = ConsoleColor.Red;
                else if (level == LOG_LEVEL.INFO)
                    Console.ForegroundColor = ConsoleColor.Yellow;
                else
                    Console.ForegroundColor = ConsoleColor.White;
                if (Console.CursorLeft != 0)
                    Console.WriteLine();

                if (newline)
                    Console.WriteLine(string.Format("{0,-9}| {1,-6}| {2,-20}| {3}",
                                    DateTime.Now.ToString("hh:mm:ss"), level, LOG_TAG, message));
                else
                {
                    Console.Write(string.Format("\r{0,-9}| {1,-6}| {2,-20}| {3}",
                                    DateTime.Now.ToString("hh:mm:ss"), level, LOG_TAG, message));
                }
                Console.ForegroundColor = ConsoleColor.White;
            }
#endif
        }

        protected void LogException(Exception ex)
        {
            string message = "Exception:\n" + ex.Message + "\nStack trace: " + ex.StackTrace;
            LOG(message, LOG_LEVEL.ERROR);
#if DEBUG
            Console.ForegroundColor = ConsoleColor.Red;
            if (Console.CursorLeft != 0)
                Console.WriteLine();
            Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") +
                    " | " + "ERROR" +
                    " | " + LOG_TAG +
                    " | " + message);
            Console.ForegroundColor = ConsoleColor.White;
#endif
        }
    }

}
